{------------------------------------------------------------------------------
#
#		Apple Macintosh Developer Technical Support
#
#		Standard File Sample Application
#
#		StdFile
#
#		StdFile.p		-		Pascal Source
#
#		Copyright © 1989 Apple Computer, Inc.
#		All rights reserved.
#
#	Versions:
#									1.00						04/89
#
#	Components:
#									StdFile.c				April 1, 1989
#									StdFile.p				April 1, 1989
#									StdFile.h				April 1, 1988
#									StdFile.r				April 1, 1988
#									StdFile.rsrc		April 1, 1988
#									CStdFile.make		April 1, 1989
#									PStdFile.make		April 1, 1988
#
OBJECTIVES
----------
This program attempts to demonstrate the following techniques:

		- Normal use of SFGetFile and SFPutFile
		- Normal use of SFPGetFile and SFPPutFile. This includes use of Custom
			Dialogs with handling of extra items through the implementation of a
			DlgHook.
				- First time initialization
				- Extra Simple buttons (Quit/Directory/ThisDir)
				- Radio buttons (file format, types of files to show)
				- Aliasing click on some buttons to clicks on other buttons
				- Regenerating the list of files displayed
		- Creating a full pathname from the reply record (using Working Directories
			or DirID)
		- Selecting a directory (ala MPW's "GetFileName -d")
		- Simple file filter (checks file type)
		- Complex file filter (looking inside the file)
		- Adding and deleting and extra List Manager lists. This is shown for both
			SFGetFile and SFPutFile.
		- Select multiple files using one of two methods
				- Replace SF's list with one of your own
				- Add a second list to the Dialog Box. This method is not shown
					explicitly. Rather, I show how to install and dispose of the actual
					list item. Inserting filenames into the list is left as an exercise
					to you, the Programmer.
		- Setting the starting Directory/Volume
		- Describe pending update event clogging


NOTES
-----
		- This application assumes existance of HFS. It makes HFS calls and
			accesses HFS data structures without first checking to see if HFS
			exists on this machine.
		- In some cases, you will see me make use of a peculiar Pascal syntax:
			"IF <expr> THEN;". This is intentional. It gets the Pascal compiler to
			discard function results that I'm not interested in.

------------------------------------------------------------------------------}
{$R-} {turn off range checking - we made sure the stove was off when we left!}

PROGRAM SFSample;

	USES Types,QuickDraw,ToolUtils,Events,Controls,Windows,Dialogs,Menus,Desk,
			 SegLoad,Files,OSEvents,Traps,Fonts,OSUtils,Resources,Memory,Packages,
			 Lists;

	CONST
		{ Low memory nasties }
		kSFSaveDisk					= $214;		{ Negative of current volume refnum [WORD]	}
		kCurDirStore				= $398;		{ DirID of current directory [LONG]					}
		kResLoad						= $A5E;		{ Boolean ResLoad setting [WORD]						}
		kTopMapHndl					= $A50;		{ 1st map in resource list [Handle]					}
		kHWCfgFlags					= $B22;		{ Used to see if A/UX is running						}

		{ Resource ID's for some dialog boxes }
		rAboutMeDLOG				= 128;
		rRealAboutMeDLOG		= 129;
		rShowSelectionDLOG	= 130;

		{ Resource ID's for Standard File Dialogs }
		rSFPGetFileDLOG			= 1000;
		rSFPPutFileDLOG			= 1001;
		rGetDirectoryDLOG		= 1002;
		rMultiFileDLOG			= 1003;
		rListsNPutFileDLOG	= 1004;
		rOptionsDLOG				= 1005;
		rOptionsSubDLOG			= 2000;

		{ STR# resources }
		rStrMisc		= 256;						{ Miscellaneous strings }
		rStrList		= 257;						{ Used to create a List Manager list }

		{ menubar resource number }
		rMenuBar		= 128;

		{ indexes for Apple menu items }
		mApple			= 128;
		iAboutMe 		= 1;

		{ indexes for File menu items }
		mFile				= 129;
		iQuit  			= 1;

		{ indexes for Edit menu items }
		mEdit				= 130;
		iUndo  			= 1;
		iCut	 			= 3;
		iCopy  			= 4;
		iPaste 			= 5;
		iClear 			= 6;

		{ indexes for Standard File menu items }
		mSFile					= 131;
		iNormalGet			= 1;
		iNormalPut			= iNormalGet + 1;
		iNormalPGet			= iNormalPut + 1;
		iNormalPPut			= iNormalPGet + 1;
		iFileFilter			= iNormalPPut + 1;
		iGetDirectory		= iFileFilter + 1;
		iMultiFile			= iGetDirectory + 1;
		iListsNPutFile	= iMultiFile + 1;
		iPutOptions			= iListsNPutFile + 1;
		iIdleUpdates		= iPutOptions + 1;
		iForceDirectory = iIdleUpdates+1;

		firstTime				= -1;  {the first time our hook is called, it is passed a -1}

		reDrawList			= 101; {returning 101 as item number will cause the FILE list
															to be recalculated}

		BTNON						= 1;   {Control value for on}
		BTNOFF					= 0;   {Control value for off}


	TYPE
		PtrToLong				= ^longint;
		PtrToWord				= ^integer;

		StringHolderHdl	= ^StringHolderPtr;
		StringHolderPtr	= ^StringHolder;
		StringHolder		= RECORD
												link: StringHolderHdl;
												title: str255;
											END;

		TwoIntsMakesALong	= RECORD
													CASE Integer OF
														1: (long: LongInt);
														2: (ints: ARRAY [0..1] OF Integer);
												END; {TwoIntsMakesALong}


	VAR
		doneFlag					: BOOLEAN;						{ set to TRUE when time to quit }
		myEvent 					: EventRecord;
		deskPart					: integer;						{ result from FindWindow }
		whichWindow				: WindowPtr;					{ used for FindWindow }
		dummy 						: Integer;
		flagPtr						: PtrToWord;
		haveAUX						: Boolean;						{ Set to TRUE if A/UX is running }
		theWorld					: SysEnvRec;
		WNEIsImplemented	: BOOLEAN;
		haveEvent					: BOOLEAN;						{ TRUE if interesting event }
		reply							: SFReply;						{ used in all SF samples }
		typeList					: SFTypeList;					{ typelist for all SF samples }
		SFSaveDisk				: PtrToWord;					{ pointer to SFSaveDisk value }
		CurDirStore				: PtrToLong;					{ pointer to CurDirStore value }
		TopMapHndl				: PtrToLong;					{ pointer to TopMapHndl handle }
		err								: OSErr;							{ used in all OS calls }

		textOnly					: Boolean;						{ for normal SFPPutFile sample }
		MySaveDisk				: integer;						{ for normal SFPPutFile sample }
		freeItemBox				: Rect;								{ for normal SFPPutFile sample }
		MyStr, MyStr2			: str255;							{ for normal SFPPutFile sample }

		MyCurDir					: Longint;						{ for SetDirectory sample }
		CurDirValid				: boolean;						{ for SetDirectory sample }

		LHandle						: ListHandle;					{ for MultiFile and ListsNPut samples }

		OptionNumber			: integer;						{ for PutOptions sample }
		OptionsDPtr				: DialogPtr;					{ for PutOptions sample }

		firstName					: StringHolderHdl;		{ for MultiFile }
		namesChanged			: boolean;						{ for MultiFile }
		needToUpdate			: boolean;						{ for MultiFile }


	PROCEDURE _DataInit;
		EXTERNAL;

(** SetUpMenus ****************************************************************)
(*
(*	Read in the menu resources and create a menu bar out of them. Draw the
(*	menu bar.
(*
(******************************************************************************)

	PROCEDURE SetUpMenus;

		VAR
			menuBar: Handle;

		BEGIN
			menuBar := GetNewMBar(rMenuBar);				{read menus into menu bar}
			IF (menuBar = NIL) THEN ExitToShell;
			SetMenuBar(menuBar);										{install menus}
			DisposHandle(menuBar);
			AddResMenu(GetMHandle(mApple), 'DRVR');	{add DA names to Apple menu}
			DrawMenuBar;
		END;


(** SetRadioButton ************************************************************)
(*
(*	Handy routine for setting the value of a radio button. Given a dialog
(*	pointer, and item number, and a state, this routine will take care of
(*	the rest.
(*
(******************************************************************************)

	PROCEDURE SetRadioButton(dialog:DialogPtr; item:integer; state:integer);

		VAR
			kind: integer;
			h: handle;
			r: rect;

		BEGIN
			GetDItem(dialog,item,kind,h,r);
			SetCtlValue(ControlHandle(h),state);
		END;


(** ShowAboutMeDialog *********************************************************)
(*
(*	Shows the obligatory vanity box. Normally shows the standard boring
(*	Developer Technical Support About box, but can be enlivened by holding
(*	the Command key when making the menu selection. Rick Blair says that in the
(*	future, we'll have better ones...
(*
(******************************************************************************)

	PROCEDURE ShowAboutMeDialog;

		VAR
			itemHit: Integer;

		BEGIN
			IF (BAND(myEvent.modifiers,cmdKey) = 0) THEN
				itemHit := Alert(raboutMeDLOG, NIL)
			ELSE
				itemHit := Alert(rRealAboutMeDLOG, NIL);
		END;


(** PathNameFromDirID *********************************************************)
(*
(*	Given a DirID and real vRefnum, this routine will create and return the
(*	full pathname that corresponds to it. It does this by calling PBGetCatInfo
(*	for the given directory, and finding out its name and the DirID of its
(*	parent. It the performs the same operation on the parent, sticking ITS
(*	name onto the beginning of the first directory. This whole process is
(*	carried out until we have processed the root directory (identified with
(*	a DirID of 2.
(*
(*	NOTE: This routine is now A/UX friendly. A/UX likes sub-directories
(*				separated by slashes in a pathname. This routine automatically
(*				uses colons or slashes as separators based on the value of the
(*				global gHasAUX.  This global must be initialized correctly for
(*				this routine to do its thing. However, because of this dependancy
(*				on the idiosyncracies of file systems, generating full pathnames
(*				for other than display purposes is discouraged; it's changed in
(*				the past when A/UX was implemented, and it may change again in
(*				the future it support for other file systems such as ProDOS,
(*				MS-DOS, or OS/2 are added.
(*
(******************************************************************************)

	FUNCTION PathNameFromDirID (DirID:longint; vRefnum:integer):str255;

		VAR
			Block : CInfoPBRec;
			directoryName, FullPathName : str255;

		BEGIN
			FullPathName := '';
			WITH block DO BEGIN
				ioNamePtr := @directoryName;
				ioDrParID := DirId;
			END;

			REPEAT
				WITH block DO BEGIN
					ioVRefNum := vRefNum;
					ioFDirIndex := -1;
					ioDrDirID := block.ioDrParID;
				END;
				err := PBGetCatInfo(@Block,FALSE);

				IF haveAUX THEN BEGIN
					IF directoryName[1] <> '/' THEN BEGIN
						{ If this isn't root (i.e. "/"), append a slash ('/') }
						directoryName := concat(directoryName, '/');
					END;
				END ELSE BEGIN
					directoryName := concat(directoryName,':');
				END;
				fullPathName := concat(directoryName,fullPathName);

			UNTIL (block.ioDrDirID = 2);

			PathNameFromDirID := fullPathName;
		END;


(** PathNameFromWD ************************************************************)
(*
(*	Given an HFS working directory, this routine returns the full pathname
(*	that corresponds to it. It does this by calling PBGetWDInfo to get the
(*	VRefNum and DirID of the real directory. It then calls PathNameFromDirID,
(*	and returns its result.
(*
(******************************************************************************)

	FUNCTION PathNameFromWD(vRefNum:longint):str255;

		VAR
			myBlock : WDPBRec;

		BEGIN

			{
			{ PBGetWDInfo has a bug under A/UX 1.1.  If vRefNum is a real vRefNum
			{ and not a wdRefNum, then it returns garbage.  Since A/UX has only 1
			{ volume (in the Macintosh sense) and only 1 root directory, this can
			{ occur only when a file has been selected in the root directory (/).
			{ So we look for this and hardcode the DirID and vRefNum. }

			IF (haveAUX) AND (vRefNum = -1) THEN BEGIN

				PathNameFromWD := PathNameFromDirID(2,-1);

			END ELSE BEGIN

				WITH myBlock DO BEGIN
					ioNamePtr := NIL;
					ioVRefNum := vRefNum;
					ioWDIndex := 0;
					ioWDProcID := 0;
				END;

			{ Change the Working Directory number in vRefnum into a real vRefnum }
			{ and DirID. The real vRefnum is returned in ioVRefnum, and the real }
			{ DirID is returned in ioWDDirID. }

				err := PBGetWDInfo(@myBlock,FALSE);

				WITH myBlock DO
					PathNameFromWD := PathNameFromDirID(ioWDDirID,ioWDVRefnum)
			END;
		END;


(** ShowSelection *************************************************************)
(*
(*	This routine accepts a string as input, prepends STR#(256,4) - which
(*	should be "The item selected was " - and shows a NoteAlert with the
(*	result.
(*
(******************************************************************************)

	PROCEDURE ShowSelection(theString:str255);

		VAR
			tStr: str255;

		BEGIN
			GetIndString(tStr,rStrMisc,4);
			ParamText(concat(tStr,theString),'','','');
			IF Boolean(NoteAlert(rShowSelectionDLOG, NIL)) THEN;
		END;


(** ShowCancelled *************************************************************)
(*
(*	Shows STR#(256,5) in a NoteAlert. This string says in English "The Cancel
(*	button was pressed."
(*																						- Douglas Hofstadter
(*
(******************************************************************************)

	PROCEDURE ShowCancelled;

		VAR
			tStr: str255;

		BEGIN
			GetIndString(tStr,rStrMisc,5);
			ParamText(tStr,'','','');
			IF Boolean(NoteAlert(rShowSelectionDLOG,NIL)) THEN;
		END;


(** doNormalGet ***************************************************************)
(*
(*	Simplest form of SFGetFile. This routine puts up a GetFile dialog with
(*	a request to show all files. When this is done, a check is made to see
(*	which of the Save or Cancel buttons were pressed. If the Save button was
(*	pressed, ShowSelection is called to display which file was selected. If the
(*	Cancel button was pressed, a dialog is shown saying so.
(*
(*	NOTE: the second parameter - the prompt string - is ignored in SFGetFile
(*				calls. As noted on page I-523 of Inside Mac, it is there for
(*				historical reasons only.
(*
(******************************************************************************)

	PROCEDURE doNormalGet;

		BEGIN
			SFGetFile(Point($00400040),		{location}
							'Space for Rent',			{vestigial string}
							NIL,									{fileFilter}
							-1,										{numtypes; -1 means all}
							typeList,							{array to types to show}
							NIL,									{dlgHook}
							reply);								{record for returned values}
			IF reply.good THEN
				ShowSelection(concat(PathnameFromWD(reply.vRefNum),reply.fName))
			ELSE
				ShowCancelled;
		END;


(** doNormalPut ***************************************************************)
(*
(*	Simplest form of SFPutFile. This routine puts up a PutFile dialog with a
(*	prompt and suggested file name.
(*
(******************************************************************************)

	PROCEDURE doNormalPut;

		BEGIN
			SFPutFile(Point($00400040),		{location}
							'Save document as:',	{prompt string}
							'Doug',								{original name}
							NIL,									{dlgHook}
							reply);								{record for returned values}
			IF reply.good THEN
				ShowSelection(concat(PathnameFromWD(reply.vRefNum),reply.fName))
			ELSE
				ShowCancelled;
		END;


(** doNormalPGet **************************************************************)
(*
(*	SFPGetFile with Dialog Hook and Simple File Filter. Depending on the value
(*	of the Global variable "textOnly", it shows either TEXT files, or TEXT and
(*	APPL files. The value of "textOnly" is determined by the states of two
(*	radio buttons that are added to the dialog box. Our dlgHook routine is used
(*	to initialize the radio buttons and handle hits on them. When there is a
(*	hit on a radio button, "textOnly" is set to either TRUE or FALSE, and a
(*	special command is sent back to Standard File, telling it to regenerate
(*	the list of file names it is displaying. This sample consists of 3 parts:
(*
(*	doNormalPGet - Called by doCommand. This routine initializes a variable
(*				for our file filter, and then calls SFPGetFile with pointers to
(*				two other routines and a resource number for a special dialog box
(*				with extra items in it.
(*
(*	SimpleFileFilter - Specified in our SFPGetFile call to be called to
(*				specify whether or not a file should be displayed. All TEXT files
(*				are displayed. If the global variable 'textOnly' is FALSE, then
(*				applications (APPL files) are also displayed. NO other files are
(*				displayed.
(*
(*	MySFGetHook - A routine that is called to handle hits on the non-standard
(*				items in our dialog box. These items are:
(*						- 2 radio buttons that affect the setting of the 'textOnly'
(*							variable,
(*						- a Quit button that causes the GetFile dialog box to be put
(*							away (like pressing Cancel), but also causes this demo to be
(*							exitted.
(*
(*				The dlgHook is also used to perform some special initialization
(*				on the items in the dialog box. Standard file does this by calling
(*				this routine with a bogus 'item' number of -1. When we get this
(*				number, we initialize our radio buttons, and change the text in the
(*				Open button.
(*
(*				The radio buttons are used to determine what files will appear in
(*				the files list. It does this by apporpriately setting the 'textOnly'
(*				variable for SimpleFileFilter, and then telling Standard File that
(*				the file list needs to be regenerated by passing back 101 as the
(*				the function result.
(*
(*				The Quit button causes the dialog box to go away by returning the
(*				'getCancel' to Standard File. It also sets the 'doneFlag' variable
(*				to TRUE for our MainEventLoop to notice.
(*
(******************************************************************************)

	FUNCTION MySFGetHook(MySFitem: integer; theDialog: DialogPtr): integer;

		CONST
			textButton			= 11;  {DITL item number of textButton}
			textAppButton		= 12;  {DITL item number of textAppButton}
			quitButton			= 13;  {DITL item number of quitButton}

		VAR
			itemToChange	: Handle;		{needed for GetDItem and SetCtlValue}
			itemBox				: Rect;			{needed for GetDItem}
			itemType			: integer;	{needed for GetDItem}
			buttonTitle		: Str255;		{needed for GetIndString}

		BEGIN
		{ MySFGetHook is a function that requires that an item number be passed		}
		{ back from it. Normally, this is the same item number that was passed		}
		{ to us, but not necessarily. For instance, clicks on the Quit button			}
		{ get translated into clicks on the Cancel button. We could also return		}
		{ values that cause the file names to be redrawn or have the whole event	}
		{ ignored. However, by default, we'll return what was sent to us.					}

			MySFGetHook := MySFitem;
			CASE MySFitem OF

				firstTime: BEGIN
				{ Before the dialog is drawn, our hook gets called with a -1. }
				{ This gives us the opportunity to change things like Button titles, etc. }

				{ Set the textAppButton to OFF, the textButton to ON }

					SetRadioButton(theDialog,textAppButton,BTNOFF);
					SetRadioButton(theDialog,textButton,BTNON);

				{ Get the Button title from the resource fork. If we can't get the }
				{ resource, we just won't change the open Button's title}

					GetIndString(buttonTitle,rStrMisc,1);
					IF buttonTitle <> '' THEN BEGIN { if we really got the resource}
						GetDItem(theDialog,getOpen,itemType,itemToChange,itemBox);
						SetCTitle(ControlHandle(itemToChange),buttonTitle);
					END;
				END; {firstTime}

				textButton: BEGIN
				{ Turn the textAppButton OFF, the textButton ON and redraw the list}
					IF NOT textOnly THEN BEGIN
						SetRadioButton(theDialog,textAppButton,BTNOFF);
						SetRadioButton(theDialog,textButton,BTNON);
						textOnly := TRUE;
						MySFGetHook := reDrawList; {we must tell SF to redraw the list}
					END;
				END;

				textAppButton: BEGIN
				{ Turn the textButton OFF, the textAppButton ON and redraw the list}
					IF textOnly THEN BEGIN
						SetRadioButton(theDialog,textButton,BTNOFF);
						SetRadioButton(theDialog,textAppButton,BTNON);
						textOnly := FALSE;
						MySFGetHook := reDrawList; {we must tell SF to redraw the list}
					END;
				END;

				quitButton: BEGIN
				{ Alias clicks on the Quit button to clicks on the Cancel Button. Also, }
				{ set 'doneFlag' to TRUE so that the MainEventLoop terminates. }
					MySFGetHook := getCancel; {Pass SF back a 'cancel Button'}
					doneFlag := TRUE;
				END;
			END;
		END;


	FUNCTION SimpleFileFilter(p: ParmBlkPtr): BOOLEAN;

		BEGIN
			SimpleFileFilter := TRUE;	   {Don't show it -- default}

		{ If we have a 'TEXT' file, or we have an 'APPL' file and 'textOnly' is }
		{ FALSE, then signify that we should show the file by returning FALSE }

			WITH p^.ioFlFndrInfo DO
				IF ((fdType = 'TEXT') OR (NOT(textOnly) AND (fdType = 'APPL'))) THEN
					SimpleFileFilter := FALSE; {Show it}
		END;


	PROCEDURE doNormalPGet;

		BEGIN
			textOnly := TRUE;
			SFPGetFile(Point($00400040),	{location}
							'Space for Rent',			{vestigial string}
							@SimpleFileFilter,		{fileFilter}
							-1,										{numtypes; -1 means all}
							typeList,							{array to types to show}
							@MySFGetHook,					{dlgHook}
							reply,								{record for returned values}
							rSFPGetFileDLOG,				{ID of Custom Dialog}
							NIL);									{ModalDialog filterProc}
		END;

(** doNormalPPut **************************************************************)
(*
(*	Normal SFPPutFile sample. This custom PutFile routine adds several new
(*	to the dialog box. There are 2 radio buttons that determine what format
(*	the user wants to save the information in. There is a Quit buttons that
(*	will cause us to leave this program. And there is a Static Text item that
(*	displays the amount of free space left on the disk. This text item is
(*	updated whenever we detect that the volume we are currently looking at is
(*	not the same as the last volume we looked at. Therefore, we maintain a
(*	variable called MySaveDisk to track this. This sample consists of 3 parts:
(*
(*	doNormalPPut - This routine, called by doCommand, calls SFPPutFile. Note
(*				the extra 'P' in the name. This toolbox call allows us to specify a
(*				customized dialog box, and dlgHook that handles hits on special
(*				items.
(*
(*	MySFPutHook - A routine that is called to handle the non-standard items
(*				in our dialog box. These items are:
(*						- 2 radio buttons that affect the setting of the 'textOnly'
(*							variable,
(*						- a Quit button that causes the GetFile dialog box to be put
(*							away (like pressing Cancel), but also causes this demo to be
(*							exitted,
(*						- a UserItem that displays the amount of free space on the
(*							current volume.
(*
(*				The dlgHook is also used to perform some special initialization
(*				on the items in the dialog box. Standard file does this by calling
(*				this routine with a bogus 'item' number of -1. When we get this
(*				number, we initialize our radio buttons, change the text in the
(*				save button, and prepare the user item by initializing the routine
(*				that will draw it and getting the text that will appear in it.
(*
(*				The radio buttons are used to determine what format the file will be
(*				saved in. They are functionless in this sample, merely changing
(*				their state and setting a global variable for possible use later.
(*
(*				The Quit button causes the dialog box to go away by returning the
(*				'putCancel' to Standard File. It also sets the 'doneFlag' variable
(*				to TRUE for our MainEventLoop to notice.
(*
(*				Finally, this routine performs one other function. When SFPPutFile
(*				is called, we make note of the current volume (the one that will
(*				be displayed in the dialog box). This routine is responsible for
(*				checking to see whenever we change volumes, and to update the
(*				Free Space notice accordingly. It does this by looking at the value
(*				stored at SFSaveDisk. Whenever that value changes, we have changed
(*				volumes and need to make a PBHGetVInfo call to find out how much
(*				room is left on the disk.
(*
(*	DrawFreeSpaceItem - used to draw a user item that displays the amount of
(*				free space left on the disk.
(*
(******************************************************************************)

	PROCEDURE DrawFreeSpaceItem(theWindow: WindowPtr; itemNo: Integer);

		BEGIN
			TextBox(Pointer(Ord(@myStr) + 1),length(myStr),freeItemBox,teJustCenter);
		END;


	FUNCTION MySFPutHook(MySFitem: Integer; theDialog: DialogPtr): Integer;

		CONST
			quitButton			= 9;   {DITL item number of quitButton}
			textButton			= 10;  {DITL item number of textButton}
			FormatButton		= 11;  {DITL item number of FormatButton}
			FreeSpaceItem		= 12;  {DITL item number of free space static text}

		VAR
			itemToChange		: Handle;				{needed for GetDItem and SetCtlValue}
			itemType				: Integer;			{needed for GetDItem}
			itemBox					: Rect;					{needed for GetDItem}
			myHPB						: HParamBlockRec;
			convert					: TwoIntsMakesALong;

		BEGIN						   {MySFHook}

		{ MySFPutHook is a function that requires that an item number be passed		}
		{ back from it. Normally, this is the same item number that was passed		}
		{ to us, but not necessarily. For instance, clicks on the Quit button			}
		{ get translated into clicks on the Cancel button. We could also return		}
		{ values that cause the file names to be redrawn or have the whole event	}
		{ ignored. However, by default, we'll return what was sent to us.					}

			MySFPutHook := MySFitem;

		{ Before the dialog is drawn, our hook gets called with a -1. }
		{ This gives us the opportunity to change things like Button titles, etc. }

			IF (MySFitem = firstTime) THEN BEGIN

				{ Set the FormatButton to ON, the textButton to OFF}

				SetRadioButton(theDialog,formatButton,BTNON);
				SetRadioButton(theDialog,textButton,BTNOFF);
				textOnly := FALSE;

				{ Get the text for the Save button from the resource fork. }
				{ If we can't get the resource, we just won't change the Button's title}
				GetIndString(myStr,rStrMisc,1);
				IF myStr <> '' THEN BEGIN {if we really got the resource}
					GetDItem(theDialog,putSave,itemType,itemToChange,itemBox);
					SetCTitle(ControlHandle(itemToChange),myStr);
				END;

				{ Set up our routine to draw our user item }
				GetDItem(theDialog,FreeSpaceItem,itemType,itemToChange,freeItemBox);
				SetDItem(theDialog,FreeSpaceItem,itemType,Handle(@DrawFreeSpaceItem),
						 freeItemBox);

				{ Get the text to be used  in our user item. }
				GetIndString(myStr2,rStrMisc,2);

			END; {if MySFItem = firstTime }

		{ Check to see if we have changed drives. If so, then we need to update }
		{ the text in our UserItem. }

			IF (MySaveDisk <> SFSaveDisk^) THEN BEGIN
				WITH myHPB DO BEGIN			{ set up the block for the PBHGetVInfo call}
					ioNamePtr := NIL;			{ we don't care about the name}
					ioVRefNum := -(SFSaveDisk^);
					ioVolIndex := 0;			{ use ioVRefNum only}
				END; {with}
				err := PBHGetVInfo(@myHPB,FALSE);

				IF (err = noErr) THEN BEGIN
					convert.ints[0] := 0;
					convert.ints[1] := myHPB.ioVFrBlk;
					NumToString((convert.long * myHPB.ioVAlBlkSiz) DIV 1024,myStr);
				END ELSE  {else Handle error - we can't get the free space size!}
					myStr := '??';

				{ Add the file size to 'K free' (or whatever) }
				myStr := concat(myStr,myStr2);

				{ Draw the new freeBlocks in our userItem }
				DrawFreeSpaceItem(theDialog,FreeSpaceItem);

				{ SFSaveDisk changed, so we need to update mySaveDisk}
				MySaveDisk := SFSaveDisk^;
			END; {IF mySaveDisk <> SFSaveDisk^}

			CASE MySFitem OF

				textButton: BEGIN
				{ Turn the FormatButton OFF, the textButton ON }
					IF NOT textOnly THEN BEGIN
						SetRadioButton(theDialog,formatButton,BTNOFF);
						SetRadioButton(theDialog,textButton,BTNON);
						textOnly := TRUE; { change our flag accordingly }
					END;	{if not textOnly}
				END;	{textOnlyButton}

				FormatButton: BEGIN
				{ Turn the textButton OFF, the FormatButton ON }
					IF textOnly THEN BEGIN
						SetRadioButton(theDialog,textButton,BTNOFF);
						SetRadioButton(theDialog,formatButton,BTNON);
						textOnly := FALSE; { change our flag accordingly }
					END;	{if not textOnly}
				END;	{FormatButton}

				quitButton: BEGIN
					MySFPutHook := putCancel; {Pass SF back a 'cancel Button'}
					doneFlag := TRUE;
				END;
			END;	{case}
		END;	{MySFHook}

	PROCEDURE doNormalPPut;

		BEGIN
			SFPPutFile(Point($00400040),	{location}
							'Save document as:',	{prompt string}
							'Doug',								{original name}
							@MySFPutHook,					{dlgHook}
							reply,								{record for returned values}
							rSFPPutFileDLOG,				{ID of custom Dialog}
							NIL);									{ModalDialog filterProc}
		END;


(** ComplexFileFilter *********************************************************)
(*
(*	Sample of a less trivial file filter. This routine is responsible for
(*	displaying only files that have 'ALRT' resources.  It shows how to
(*	correctly look inside a file for qualifying information, and demonstrates
(*	how to get around a particular quirk when attempting multiple access to
(*	the resource fork.
(*
(*	In order to save time, the first thing we do is turn off resource loading;
(*	we just want to count the number of ALRT resources, and don't care about
(*	the actual data itself. So I save off the current ResLoad setting, and
(*	then set it to FALSE.
(*
(*	I then prepare to open the resource fork of the file. However, I can't
(*	simply call OpenResFile. OpenResFile only works for files that are in
(*	the default directory. Standard file makes HFS calls when it can, and
(*	explicitly uses DirIDs when getting file names and other information.
(*	Therefore, I can't count on the default directory being set to the dir-
(*	ectory that we are examining. In order to call OpenResFile, I have to set
(*	the default directory.
(*
(*	So I take the VRefNum and DirID passed to me, and I create a Working
(*	Directory from them. I save the old default directory, and then set it
(*	to the WD I just created.
(*
(*	So far, so good. I am now ready to open the resource fork of the file,
(*	call Count1Resources (which limits enumeration to the current file only,
(*	excluding other files in the resource chain), and close the file. But
(*	wait! I must make sure that I am not closing a file that I don't really
(*	want to close. For example, one of the files I may be examining may be
(*	myself. If I were to blindly close that file, I would be closing my CODE
(*	resources, removing them from memory. Empirical experience has told me that
(*	this is not good.
(*
(*	So I have to make sure that I don't close my own resource fork. I could
(*	possibly check the refnum that I get back from OpenResFile against one
(*	that I could get by calling CurResFile at the start of the program. If
(*	they match, then I am looking at my own file, and I shouldn't close it. But
(*	this doesn't work always. For instance, I also want to make sure that I
(*	don't close the System Resource file when I point and click my way into
(*	the System Folder. What if there are DA's open? I don't want to close them.
(*	How about the Fonts/DA's file used by Suitcase? What about the DeskTop
(*	file? Basically, I need a way to determine on the fly if a resource file
(*	is already open before I try to open it myself.
(*
(*	Well, there is a way. Within the parameter block passed to me is a field
(*	called ioFlAttrib. It is described on page IV-122 of Inside Mac. As
(*	described there, bit 2 contains the state of the resource fork. If the fork
(*	is open, then the bit is set. All that is necessary for me to do is check
(*	the state of this bit before I open the file. If the bit is clear, then
(*	I need to close the file when I am done.
(*
(*	But wait! There's more. This technique - alas! - will not work under
(*	MultiFinder! Why? Because this bit is set even if another program in
(*	another partition has the resource fork open. I would test that bit, find
(*	that the file is open, open the file for myself, count the number of
(*	resources, and then leave the file open. This now leaves another resource
(*	file in the resource chain - one that wasn't there before and that
(*	shouldn't be there now. Another technique is needed.
(*
(*	Fortunately, one is at hand. On page I-115 of Inside Mac is a brief
(*	description of a low-memory variable called TopMapHndl. This contains
(*	a handle to the resource map of the first resource file in the chain.
(*	Since a resource file is made the current resource file when OpenResFile
(*	is called if it is actually being opened, we can test the value of
(*	TopMapHndl to see if the file was really opened. If the value of TopMapHndl
(*	changes after OpenResFile, then the resource fork was open for this
(*	application, and should be closed when we are done with it. If TopMapHndl
(*	doesn't change, then no new resource maps were added to the chain; no
(*	new files were opened. Therefore, the file doesn't need to be closed later.
(*	So far, this solution seems to be the best.
(*
(*	After doing all of this, we are almost done. All that needs to be done is
(*	restore the default directory and the value of ResLoad to their previous
(*	settings. When we have done so, we can return to Standard File.
(*
(******************************************************************************)

	FUNCTION ComplexFileFilter(p: ParmBlkPtr): BOOLEAN;

		TYPE
			LongPtr = ^longint;
			BoolPtr = ^Boolean;

		VAR
			refnum,tRefnum : integer;
			WDRec : WDPBRec;
			oldVol,newVol : ParamBlockRec;
			tBPtr : BoolPtr;
			oldResLoad, closeIt : Boolean;
			tHandle: handle;

		BEGIN

		{ Save the current setting of ResLoad. Then set it to FALSE. }

			tBPtr := BoolPtr(kResLoad);
			oldResLoad := tBPtr^;
			SetResLoad(FALSE);

			ComplexFileFilter := TRUE;		{ Don't show it -- default }

		{ Save the current default directory/volume. }

			oldVol.ioNamePtr := NIL;
			err := PBGetVol(@oldVol,FALSE);

		{ Create a working directory for the directory we are examining. }

			WITH WDRec DO BEGIN
				ioNamePtr := NIL;
				ioVRefNum := p^.ioVRefNum;
				ioWDProcID := longint('ERIK');
				ioWDDirID := CurDirStore^;
			END;
			err := PBOpenWD(@WDRec,FALSE);

		{ Set the Working Directory we just created to be the default. }

			WITH newVol DO BEGIN
				ioNamePtr := NIL;
				ioVRefNum := WDRec.ioVRefNum;
			END;
			err := PBSetVol(@newVol,FALSE);

		{ Check the current value of TopMapHndl, open the resource file, and }
		{ check it again. If it changed, then note that we should close this }
		{ file later when we are done with it. }

			tHandle := handle(TopMapHndl^);
			refnum := OpenResFile(str255(p^.ioNamePtr^));
			IF (tHandle <> handle(TopMapHndl^)) THEN
				closeIt := TRUE
			ELSE
				closeIt := FALSE;

		{	If we successfully opened the file, then count the number of ALRT }
		{ resources in it. Call UseResFile to make sure that the file we just }
		{ 'opened' is the current one. We have to do this, as OpenResFile will }
		{ not make the file the current one if it is already open. So save the }
		{ current resFile, (possibly) change it to the one we want, read the }
		{ number of ALRT resources in it, and then set the current resource }
		{ file back to what it was. Finally, if we need to close the file we }
		{ opened, do so. }

			IF (ResError = noErr) THEN BEGIN
				tRefNum := CurResFile;
				UseResFile(refNum);
				IF (Count1Resources('ALRT') > 0) THEN ComplexFileFilter := FALSE;
				UseResFile(tRefNum);
				IF closeIt THEN CloseResFile(refnum);
			END;

		{ All done! Reset the default directory and ResLoad, and then blow. }

			err := PBSetVol(@oldVol,FALSE);
			err := PBCloseWD(@WDRec,FALSE);
			SetResLoad(oldResLoad);

		END;

	PROCEDURE doFileFilter;

		BEGIN
			SFGetFile(Point($00400040),		{location}
							'Space for Rent',			{vestigial string}
							@ComplexFileFilter,		{fileFilter}
							-1,										{numtypes; -1 means all}
							typeList,							{array to types to show}
							NIL,									{dlgHook}
							reply);								{record for returned values}
		END;


(** doGetDirectory ************************************************************)
(*
(*	Shows how to modify SFGetFile to allow you to select a directory. This
(*	mimics the "GetFileName -d" function of MPW. As a matter of fact, the
(*	DLOG and DITL used in this sample are taken directly out of MPW.
(*
(*	There are 2 major additions in the dialog used in this sample:
(*				- a Simple button that lets one select the directory that is
(*					currently highlighted in the list of directories,
(*				- a Simple button at the top of the dialog that lets the user select
(*					the directory that we are currently *IN*.
(*
(*	No new techniques are really used in this sample. Hits on the two simple
(*	buttons are handled by a dlgHook called MyGetDirHook. Depending on
(*	which button is hit, we set the global variable MyCurDir to either
(*	reply.fType (for the currently highlighted directory) or CurDirStore^ (for
(*	the directory that we are currently in). We then simulate a hit on the
(*	Cancel button so that Standard File will return to our application.
(*
(*	However, when we get back to our application, there is no way for it to
(*	determine if we simulated a click on the Cancel Button, or if the user
(*	actually clicked on it; reply.good will be FALSE in either case. So we also
(*	set the value of global variable CurDirValid to TRUE if we only simulated
(*	a click on Cancel.
(*
(*	We also use the "Item = -1" feature to set up a prompt string and init-
(*	alize the value of CurDirValid to FALSE.
(*
(******************************************************************************)

	FUNCTION MyGetDirHook(item: integer; dPtr: DialogPtr):integer;

		CONST
			{ Equates for the items that I've added }
			getDirButton = 11;
			getDirNowButton = 12;
			getDirMessage = 13;

		VAR
			messageTitle: str255;
			h:Handle;
			kind:integer;
			r:rect;

		BEGIN
		{ By default, return the item passed to us. }
			MyGetDirHook := item;

			CASE item OF
				firstTime: BEGIN

				{ Read in the prompt string from the resource fork, and initialize }
				{ CurDirValid to FALSE. }

					GetIndString(messageTitle,rStrMisc,3);
					GetDItem(dPtr,getDirMessage,kind,h,r);
					SetIText(h,messageTitle);
					CurDirValid := FALSE;
				END;
				getDirButton: BEGIN
					IF LONGINT(reply.fType) <> 0 THEN BEGIN
						MyCurDir := LONGINT(reply.fType);
						myGetDirHook := getCancel;
						CurDirValid := TRUE;
					END;
				END;
				getDirNowButton: BEGIN
					MyCurDir := CurDirStore^;
					myGetDirHook := getCancel;
					CurDirValid := TRUE;
				END;
			END;
		END;


	FUNCTION FoldersOnly(p:ParmBlkPtr):BOOLEAN;

	{ Normally, folders are ALWAYS shown, and aren't even passed to				}
	{ this file filter for judgement. Under such circumstances, it is			}
	{ only necessary to blindly return TRUE (allow no files whatsoever).	}
	{ However, Standard File is not documented in such a manner, and			}
	{ this feature may not be TRUE in the future. Therefore, we DO check	}
	{ to see if the entry passed to us describes a file or a directory.		}

		BEGIN
			FoldersOnly := TRUE;
			IF BTst(p^.ioFlAttrib,4) THEN FoldersOnly := FALSE;
		END;


	PROCEDURE doGetDirectory;

		BEGIN
			SFPGetFile(Point($00400040),	{location}
							'Space for Rent',			{vestigial string}
							@FoldersOnly,					{fileFilter}
							-1,										{numtypes; -1 means all}
							typeList,							{array to types to show}
							@MyGetDirHook,				{dlgHook}
							reply,								{record for returned values}
							rGetDirectoryDLOG,
							NIL);
			IF CurDirValid THEN
				ShowSelection(PathnameFromDirID(MyCurDir,-(SFSaveDisk^)))
		END;


(** MultiFile *****************************************************************)
(*
(*	WARNING! The following is an experiment that failed! I was playing around
(*						with different ways for implementing a facility for multiple
(*						file handling. This was one of them. As with all experiments that
(*						fail, it's pretty ugly. The following should NOT be used as a
(*						sample on how to do things right, but as a lesson on how NOT to
(*						do things.
(*
(*	This sample is an attempt to implement the facilty to let the user choose
(*	more than one file at once. It does this by remembering all of the files
(*	passed to the fileFilter, and using those names to create a new, application
(*	governed filename list that will replace the one displayed by Standard File.
(*	However, this experiment seems doomed to failure, for the following reasons:
(*
(*			- Icons are not displayed next to the names of files. The resulting
(*				presentation is more disconcerting that I thought.
(*			- The current directory button (the one displayed above the list of
(*				filenames) disappears. This seems due to the fact that that pop-up
(*				menu-like item is not actually a dialog item, and is somehow attached
(*				to the list of filenames handled by Standard File. Since I move that
(*				list off of the window so that I can display my own, the current
(*				directory button seems to go with it.
(*			- Updating the list is difficult. The normal sequence of events would
(*				desirably be: 1) have the fileFilter create a linked list of names
(*				to appear in the dialog, 2) insert those names into the List Manager
(*				list, and 3) display the list. However, by following this outine,
(*				the list does not get updated, as the update region for that area
(*				is empty. So, at some time, we need to invalidate the area
(*				that holds our list. But there is no convenient place in which to
(*				do that. We can't call InvalRect within our fileFilter routine, as
(*				we don't have the DialogPtr at the time (needed to get the bounding
(*				rectangle of the list item). Neither can we call InvalRect within
(*				step 2, as we are between BeginUpdate/EndUpdates, and calling
(*				InvalRect would be useless.
(*			- We can't display directories. They are not offered to the judgement
(*				of our fileFilter, and hence cannot be added to our list.
(*			- Key presses would have do be handled manually by our dlgHook.
(*
(*	Our thesis is implemented as described, with all of the above limitations.
(*	What follows is a description of the routines used:
(*
(*	ScoopNames - The fileFilter routine that is responsible for remembering
(*				the names of all the files passed to it by Standard file. The names
(*				are stored in a singly linked list of handles. Each handle contains
(*				a Str255 to hold the name, and room for the handle for the next name.
(*				The root of the list is stored in the global variable 'firstName'.
(*				The list is used by SetNames to set the data for the list cells.
(*
(*	MultiFileHook - Creates the list and installs it as a user item. Also,
(*				we check on NULL events to see if our list needs to be updated. If
(*				so, we call InvalRect to force it to be redrawn. Finally, this
(*				routine disposes of the list if Open or Cancel are pressed.
(*
(*	MFModalFilter - Calls LClick for MouseDowns on the list.
(*
(*	MFDrawList - Redraws the list. If there are new names to be displayed, then
(*				they are inserted into the cells. Then LUpdate is called to redraw
(*				the list.
(*
(*	SetNames - Called by MFDrawList when FirstName is <> NIL, indicating that
(*				there are new names to be displayed. We first get a count of the
(*				number of names in the linked list, and the List Manager list is
(*				adjusted accordingly. Then, the linked list is traversed, with each
(*				name being inserted into its appropriate cell. The nodes of the
(*				linked list are disposed of along the way. When everything is done,
(*				the List Manager list is appropriately set up, and the linked list is
(*				disposed of, with FirstName = NIL.
(*
(******************************************************************************)

	PROCEDURE SetNames;

		VAR
			nextName: StringHolderHdl;
			count: integer;
			delta: integer;
			theCell: Cell;

		BEGIN
			nextName := firstName;
			count := 0;
			IF (nextName <> NIL) THEN BEGIN
				REPEAT
					count := count + 1;
					nextName := nextName^^.link;
				UNTIL (nextName = NIL);
			END;
			delta := count - LHandle^^.databounds.bottom;
			IF (delta < 0) THEN		{need to remove this many cells}
				LDelRow(-delta,0,LHandle)
			ELSE IF (delta > 0) THEN  {need to add this many cells}
				IF boolean(LAddRow(delta,LHandle^^.databounds.bottom,LHandle)) THEN;
			theCell.h := 0;
			theCell.v := count-1;
			REPEAT
				HLock(handle(firstName));
				WITH firstName^^ DO
					LSetCell(ptr(@title[1]),integer(title[0]),theCell,LHandle);
				HUnlock(handle(firstName));
				nextName := firstName^^.link;
				DisposHandle(handle(firstName));
				firstName := nextName;
				theCell.v := theCell.v - 1;
			UNTIL (firstName = NIL);
			namesChanged := FALSE;
		END;


	PROCEDURE MFDrawList(theWindow:WindowPtr; item:integer);

		CONST
			ListItem = 11;

		VAR
			kind: integer;
			h: handle;
			r: rect;

		BEGIN
			IF (item = ListItem) THEN BEGIN
				GetDItem(theWindow,ListItem,kind,h,r);
				EraseRect(r);
				FrameRect(r);
				LUpdate(theWindow^.visRgn,LHandle);
				IF (namesChanged) THEN
					SetNames;
			END;
		END;

	FUNCTION MFModalFilter(theDialog:DialogPtr;
													VAR theEvent:EventRecord;
													VAR itemHit:integer):boolean;
		VAR
			doubleClick : boolean;
			localPt : Point;

		BEGIN
			IF (theEvent.what = mouseDown) THEN BEGIN
				localPt := theEvent.where;
				GlobalToLocal(localPt);
				doubleClick := LClick(localPt,theEvent.modifiers,LHandle);
				{ should check for double clicks here }
			END;
			MFModalFilter := FALSE;
		END;


	FUNCTION MultiFileHook(item: integer; dPtr: DialogPtr):integer;

		CONST
			ListItem = 11;

		VAR
			kind: integer;
			h: handle;
			r: rect;
			rView,dataBounds: rect;

		BEGIN
			CASE item OF
				firstTime: BEGIN
						GetDItem(dPtr,ListItem,kind,h,r);
						SetDItem(dPtr,ListItem,kind,handle(@MFDrawList),r);
						rView := r;
						rView.right := rView.right - 15;
						InsetRect(rView,1,1);
						dataBounds.topLeft := Point(0);
						dataBounds.bottom := 0;
						databounds.Right := 1;
						LHandle := LNew(rView,			{ position in window }
														dataBounds,	{ initial size of array }
														Point(0),		{ cell size (0 = default) }
														0,					{ resource ID of LDEF }
														dPtr,				{ window pointer }
														TRUE,				{ drawit }
														FALSE,			{ has grow }
														FALSE,			{ scrollHoriz }
														TRUE);			{ scrollVert }
					END;
				100: BEGIN
						IF (needToUpdate) THEN BEGIN
							GetDItem(dPtr,ListItem,kind,h,r);
							InvalRect(r);
							needToUpdate := FALSE;
						END;
					END;
				getOpen,getCancel:BEGIN
						LDispose(LHandle);	{ Our linked list of names was already disposed }
																{ of in SetNames, so nothing else to do here... }
					END;
			END;
			MultiFileHook := item;
		END;

	FUNCTION ScoopNames(pb: ParmBlkPtr):BOOLEAN;

		VAR
			nextName : stringHolderHdl;

		BEGIN
			nextName := StringHolderHdl(NewHandle(sizeof(stringHolder)));
			HLock(handle(nextName));
			nextName^^.title := str255(pb^.ioNamePtr^);
			nextName^^.link := firstName;
			firstName := nextName;
			HUnlock(handle(nextName));
			namesChanged := TRUE;
			needToUpdate := TRUE;

			ScoopNames := FALSE; {show everything}
		END;

	PROCEDURE doMultiFile;

		BEGIN
			firstName := NIL;
			namesChanged := FALSE;
			SFPGetFile(Point($00400040),	{location}
							'Space for Rent',			{vestigial string}
							@ScoopNames,					{fileFilter}
							-1,										{numtypes; -1 means all}
							typeList,							{array to types to show}
							@MultiFileHook,				{dlgHook}
							reply,								{record for returned values}
							rMultiFileDLOG,				{ID of Custom Dialog}
							@MFModalFilter);			{ModalDialog filterProc}
		END;


(** doListsNPutFile ***********************************************************)
(*
(*	This sample demonstrates putting a List Manager list into an SFPPutFile
(*	dialog, describes a problem that can occur by doing so, and shows one
(*	solution to the problem.
(*
(*	Putting another list into a Standard File dialog can be useful for a number
(*	of reasons. One of them was shown above, where we attempted to replace
(*	Standard File's list with one of our own. However, the failure we met with
(*	there forces us to look for another solution. This solution could possibly
(*	take place in the form of TWO lists: one supplied by Standard file, and the
(*	other handled by us to hold the multiple file names.
(*
(*	While the actual logistics of putting the selected filenames into the list
(*	as they are selected is left to you, the Programmer, I do show how to
(*	create and handle the list.
(*
(*	While *I* can't think of a reason for wanting to put a second list into
(*	a PutFile dialog, it is possible that you may wish to. However, there is
(*	a subtle problem that arises if you do: that of determining when to
(*	dispose of the list. In the MultiFile example above, we were perfectly
(*	justified in disposing of the list when the user clicked on Open or
(*	Cancel. However, in the following example, if were to dispose of the list
(*	when the user clicked on Save, we may hit a snag. It is possible for the
(*	user to specify the name of a file that alreay exists. When that happens,
(*	s/he is presented with another dialog that asks if they are sure of what
(*	they are doing. At that point, the user could press Cancel, and return us
(*	to the PutFile dialog, SANS our 2nd list! Neither can we defer disposing
(*	of list until Standard File is done and returns to our application, as the
(*	Dialog box we were using is gone, and the List Manager try to perform an
(*	InvalRect on a non-existant window when it erases its list.
(*
(*	There are two solutions to this problem:
(*
(*		1) Implement the list as a Custom Control. Then, when it somes time to
(*				close the dialog box, the custom control will get called with a
(*				dispCntl message. It can take that opportunity to call LDispose.
(*
(*				This technique is not presented here, as it is more appropriate
(*				for a Custom Control Sample program.  However, it is the suggested
(*				way to proceed.
(*
(*		2) Ensure that the confirmation dialog box never comes up under Standard
(*				File's control. By calling it ourself, we know what the user chooses.
(*				If the user presses OK, we delete the offending file, dispose of our
(*				list, and return to Standard File. If the user presses Cancel, we
(*				change the click on the Save button into a NULL event. This is the
(*				algorithm we use below.
(*
(*				There is a major disadvantage with this approach, however. With it,
(*				we cannot implement a safe saving procedure. Normally, the best way
(*				to save a file (disk space permitting), is to save the data to a
(*				temporary file, delete the original, and then rename the temporary
(*				file to that of the original. However, with approach #2, the file
(*				is deleted well before it is safe to do so.
(*
(*				This routine *could* be reworked to avoid this problem. For instance,
(*				instead of being deleted, the offending file could be renamed to
(*				something else or moved to another directory.
(*
(******************************************************************************)

	PROCEDURE LNPFDrawList(theWindow:WindowPtr; item:integer);

		CONST
			ListItem = 9;

		VAR
			kind: integer;
			h: handle;
			r: rect;

		BEGIN
			IF (item = ListItem) THEN BEGIN
				GetDItem(theWindow,ListItem,kind,h,r);
				FrameRect(r);
				LUpdate(theWindow^.visRgn,LHandle);
			END;
		END;

	FUNCTION LNPFModalFilter(theDialog:DialogPtr;
													VAR theEvent:EventRecord;
													VAR itemHit:integer):boolean;
		VAR
			localPt : Point;

		BEGIN
			IF (theEvent.what = mouseDown) THEN BEGIN
				localPt := theEvent.where;
				GlobalToLocal(localPt);
				IF LClick(localPt,theEvent.modifiers,LHandle) THEN;
			END;
			LNPFModalFilter := FALSE;
		END;


	FUNCTION ListsNPutFileHook(item: integer; dPtr: DialogPtr):integer;

		CONST
			ListItem = 9;
			ExistingFileALRT = -3996;

		VAR
			kind: integer;
			h: handle;
			r: rect;
			rView,dataBounds: rect;
			count: integer;
			entry: str255;
			theCell: Cell;
			choice: integer;
			fndrInfo: FInfo;
			WDRec: WDPBRec;
			AlertHandle: handle;
			dLoc: point;

		BEGIN
			ListsNPutFileHook := item;
			CASE item OF
				firstTime: BEGIN
						GetDItem(dPtr,ListItem,kind,h,r);
						SetDItem(dPtr,ListItem,kind,handle(@LNPFDrawList),r);
						rView := r;
						rView.right := rView.right - 15;
						InsetRect(rView,1,1);
						dataBounds.topLeft := Point(0);
						dataBounds.bottom := 0;
						databounds.Right := 1;
						LHandle := LNew(rView,			{ position in window }
														dataBounds,	{ initial size of array }
														Point(0),		{ cell size (0 = default) }
														0,					{ resource ID of LDEF }
														dPtr,				{ window pointer }
														TRUE,				{ drawit }
														FALSE,			{ has grow }
														FALSE,			{ scrollHoriz }
														TRUE);			{ scrollVert }
						theCell := point(0);
						REPEAT
							GetIndString(entry,rStrList,theCell.v + 1);
							IF (entry <> '') THEN BEGIN
								IF boolean(LAddRow(1,LHandle^^.dataBounds.bottom,LHandle)) THEN;
								LSetCell(@entry[1],integer(entry[0]),theCell,LHandle);
							END;
							theCell.v := theCell.v + 1;
						UNTIL (entry = '');
					END;

				putSave: BEGIN
						WITH WDRec DO BEGIN
							ioNamePtr := NIL;
							ioVRefNum := -(SFSaveDisk^);
							ioWDProcID := longint('ERIK');
							ioWDDirID := CurDirStore^;
						END;
						err := PBOpenWD(@WDRec,FALSE);

						err := GetFInfo(reply.fName,WDRec.ioVRefNum,fndrInfo);
						IF (err = noErr) THEN BEGIN
							ParamText(reply.fName,'','','');

						{ Before bringing up the Alert that asks for confirmation, we			}
						{ have to relocate it. To start with, the window is located at		}
						{ 0,0 in the Window Manager port. To move the Alert Window, read	}
						{ the resource into memory and change the boundsRect field so			}
						{ that its topleft is at 12,100 within Standard File's dialog.		}

							AlertHandle := GetResource('ALRT',ExistingFileALRT);
							dLoc := dPtr^.portRect.topleft;		{ get global location of SF's }
							LocalToGlobal(dLoc);							{ dialog box. }
							WITH AlertTHndl(AlertHandle)^^.boundsRect DO BEGIN
								right		:= right	- left;		{ get width and height of the Alert }
								bottom	:= bottom	- top;		{ into botRight. }
								left		:= dLoc.h	+ 12;			{ Change Alert.TopLeft to SF.Dlog.TopLeft }
								top			:= dLoc.v	+ 100;		{ plus 12,100. }
								right		:= right	+ left;		{ Adjust Alert.BotRight accordingly. }
								bottom	:= bottom	+ top;
							END;
							choice := Alert(ExistingFileALRT,NIL);
							IF (choice = Cancel) THEN BEGIN  {the OK button is in the Cancel slot}
								err := FSDelete(reply.fName,WDRec.ioVRefNum);
								LDispose(LHandle);
							END ELSE BEGIN
								ListsNPutFileHook := 100;	{Change "Save" into null event}
							END;
						END;
					END;
				putCancel:BEGIN
						LDispose(LHandle);
					END;
			END;
		END;


	PROCEDURE doListsNPutFile;

		VAR
			vRefNum: integer;

		BEGIN
			err := GetVol(NIL,vRefNum);
			err := Create('Doug',vRefNum,'KAAR','APPL');
			SFPPutFile(Point($00400040),	{location}
							'Save document as:',	{prompt string}
							'Doug',								{original name}
							@ListsNPutFileHook,		{dlgHook}
							reply,								{record for returned values}
							rListsNPutFileDLOG,		{ID of custom Dialog}
							@LNPFModalFilter);		{ModalDialog filterProc}
		END;


(** doPutOptions **************************************************************)
(*
(*	Apple's suggested options box - A modest proposal.
(*
(*	With the proliferation of applications and different file types, many
(*	developers are allowing the user to select from various file formats they
(*	want to save their data as. In an effort to maintain consistancy among
(*	all applications in this respect, we are presenting a sample interface for
(*	letting the user select the file type they want.
(*
(*	This is done by adding 2 items to the PutFile dialog box. One is a text
(*	item that displays the file format that the file will be saved as. The
(*	second is an "OptionsÉ" button that brings up another dialog box. This
(*	dialog box conatins a series of radio buttons next to the names of the
(*	various file formats supported by the application. Also in the second
(*	dialog box are OK and Cancel buttons. After a selection has been made
(*	by the user, the text in the PutFile dialog is updated accordingly.
(*
(******************************************************************************)

	PROCEDURE SetFormatString(number:integer; theDialog:DialogPtr);

		CONST
			FormatString = 10;

		VAR
			kind: integer;
			h: handle;
			r: rect;
			title: str255;

		BEGIN
			GetDItem(OptionsDPtr,OptionNumber,kind,h,r);
			GetCTitle(ControlHandle(h),title);
			GetDItem(theDialog,formatString,kind,h,r);
			SetIText(h,title);
		END;


	PROCEDURE DrawFrame(theWindow: WindowPtr; itemNo: integer);

		VAR
			kind: integer;
			h: handle;
			r: rect;
			ps: PenState;

		BEGIN
			GetDItem(theWindow,itemNo,kind,h,r);
			GetPenState(ps);
			PenSize(2,2);
			FrameRect(r);
			SetPenState(ps);
		END;

	PROCEDURE doOptionsDialog(VAR item:integer);

		VAR
			newItem: integer;
			itemHit: integer;

		BEGIN
			newItem := OptionNumber;
			SelectWindow(OptionsDPtr);
			ShowWindow(OptionsDPtr);
			REPEAT
				ModalDialog(NIL,itemHit);
				IF ((itemHit <> newItem) AND (itemHit > 2)) THEN BEGIN
					SetRadioButton(OptionsDPtr,newItem,BTNOFF);
					SetRadioButton(OptionsDPtr,itemHit,BTNON);
					newItem := itemHit;
				END;
			UNTIL ((itemHit = OK) OR (itemHit = Cancel));
			HideWindow(OptionsDPtr);
			IF (itemHit = OK) THEN
				item := newItem;
		END;

	FUNCTION PutOptionsHook(item: Integer; theDialog: DialogPtr): Integer;

		CONST
			FirstRadioButton = 3;
			OptionsButton = 9;
			FrameItem	= 10;

		VAR
			kind: integer;
			h: handle;
			r: rect;

		BEGIN
			PutOptionsHook := item;
			CASE item OF
				firstTime: BEGIN
						OptionNumber := FirstRadioButton;
						SetRadioButton(OptionsDPtr,OptionNumber,BTNON);
						SetFormatString(OptionNumber,theDialog);
						GetDItem(OptionsDPtr,FrameItem,kind,h,r);
						SetDItem(OptionsDPtr,FrameItem,kind,handle(@DrawFrame),r);
					END;
				OptionsButton: BEGIN
						doOptionsDialog(OptionNumber);
						SetFormatString(OptionNumber,theDialog);
					END;
			END;
		END;


	PROCEDURE doPutOptions;

		BEGIN
			OptionsDPtr := GetNewDialog(rOptionsSubDLOG,NIL,WindowPtr(-1));
			SFPPutFile(Point($00400040),	{location}
							'Save document as:',	{prompt string}
							'Doug',								{original name}
							@PutOptionsHook,			{dlgHook}
							reply,								{record for returned values}
							rOptionsDLOG,					{ID of custom Dialog}
							NIL);									{ModalDialog filterProc}
			DisposDialog(OptionsDPtr);
		END;

(** doIdleUpdates *************************************************************)
(*
(*	There is a problem that Standard File has with updates pending in
(*	background windows in the current application partition.
(*
(*	Standard File calls ModalDialog at the heart of its Main Event Loop.
(*	ModalDialog calls GetNextEvent, and then calls a filterProc internal
(*	to Standard File. This filterProc performs some processing on the event,
(*	(like handling hits on the filename list, the Current Directory button, and
(*	the Disk Name Icon), and then calls the filterProc specified in SFPxxxFile
(*	calls.
(*
(*	Another of the things that the internal filterProc does is look for NULL
(*	events. When one is found, the filterProc returns 100 as the "itemHit".
(*	This forces ModalDialog to return to SF, which in turn can now call
(*	the dlgHook with the bogus 100 item number, indicating that idle time
(*	processing can be performed.
(*
(*	The problem occurs when there are updates pending in windows open in
(*	the current application's partition. These updates will 'clog' the event
(*	queue. When ModalDialog calls GetNextEvent, it will get the update
(*	event. However, there is no way for ModalDialog to respond to it. It
(*	therefore passes the event off to the filterProc, which is SF's internal
(*	one. This filterProc doesn't know how to handle it either. Normally,
(*	the event would be ignored at this point, and the update event would be
(*	unresolved. Control returns back to ModalDialog, which calls GetNextEvent
(*	again, and gets the same udpate event back. NULL events will not get
(*	returned by GetNextEvent, and, hence, the dlgHook will never get called
(*	with itemHit=100.
(*
(*	This situation can be solved by providing your own filterProc
(*	procedure to be called after SF's internal filterProc. It will be this
(*	routine's responsibility to check for update events, and handle them
(*	appropriately. Usually, this would mean that the update procedure
(*	within the application would be called, and the update could be cleared.
(*	However, the filterProc could also just handle update events in the same
(*	way that Standard File's filterProc handles NULL events. This is done
(*	by returning 100 in the ItemHit parameter and TRUE as the function result.
(*	This is the approach taken by the sample below.
(*
(******************************************************************************)


	{ This is our dlgHook routine. All it does is wait around for NULL events		}
	{ and draws the current time when it gets one. Note that this routine will	}
	{ NOT get called with item=100 if we did not have the filter procedure			}
	{ below. }

	FUNCTION IdleTimeHook(item: integer; dlg:DialogPtr): integer;

		VAR
			dateTime:LONGINT;
			result: str255;

		BEGIN
			IdleTimeHook := item;
			IF (item = 100) THEN BEGIN
				TextMode(srcCopy);
				MoveTo(250,15);
				GetDateTime(dateTime);
				IUTimeString(dateTime,{wantSeconds=}TRUE,result);
				result := concat(result,'  '); {pad with spaces to erase longer strings}
				DrawString(result);
			END;
		END;

	FUNCTION filter(dlg:DialogPtr;
									VAR evt: EventRecord;
									VAR itemHit: integer):  boolean;

	{ If we get an update event for a window other than the dialog box, change }
	{ it to a NULL event, and tell ModalDialog that we handled it. }

		BEGIN
			filter := FALSE;
			IF ((evt.what = updateEvt) and (DialogPtr(evt.message) <> dlg)) THEN BEGIN
				itemHit := 100;
				filter := TRUE;
			END;
		END;

	PROCEDURE doIdleUpdates;

		VAR
			r: rect;
			tPtr: WindowPtr;

		BEGIN
		{ Create a window with a non-empty update region. }
			r.top := 40; r.left := 10; r.bottom := 340; r.right := 500;
			tPtr := NewWindow(NIL,						{ Window storage }
												r,							{ bounding rectangle }
												'Un-updated Window',
												TRUE,						{ is visible }
												documentProc,		{ procID }
												Pointer(-1),		{ bring it up on top }
												TRUE,						{ has goaway }
												0);							{ refcon }

			SFPGetFile(Point($00400040),	{location}
							'Space for Rent',			{vestigial string}
							NIL,									{fileFilter}
							-1,										{numtypes; -1 means all}
							typeList,							{array to types to show}
							@IdleTimeHook,				{dlgHook}
							reply,								{record for returned values}
							-4000,								{ID of Normal Dialog}
							@filter);							{ModalDialog filterProc}

			DisposeWindow(tPtr);
		END;


(** doForceDirectory **********************************************************)
(*
(*	This is a quick sample that shows how to set the initial directory that
(*	Standard File comes up with. Basically, this is done by storing apporpriate
(*	values into SFSaveDisk and CurDirStore. In this example, I force the
(*	directory to be the Blessed Folder as specified by SysEnvirons.
(*
(******************************************************************************)

	PROCEDURE doForceDirectory;

		VAR
			pb: WDPBRec;
			err: OSErr;

		BEGIN
			WITH pb DO BEGIN
				ioNamePtr := NIL;
				ioVRefNum := theWorld.sysVRefNum;
				ioWDIndex := 0;
				ioWDProcID := 0;
			END;
			err := PBGetWDInfo(@pb,FALSE);

			CurDirStore^ := pb.ioWDDirID;
			SFSaveDisk^ := -pb.ioWDVRefNum;

			SFGetFile(Point($00400040),		{location}
							'Space for Rent',			{vestigial string}
							NIL,									{fileFilter}
							-1,										{numtypes; -1 means all}
							typeList,							{array to types to show}
							NIL,									{dlgHook}
							reply);								{record for returned values}
			IF reply.good THEN
				ShowSelection(concat(PathnameFromWD(reply.vRefNum),reply.fName))
			ELSE
				ShowCancelled;
		END;

(************************ Performed the selected command *************************)

	PROCEDURE DoCommand(mResult: Longint);


		VAR
			theItem: Integer;
			theMenu: Integer;
			name: str255;
			temp: Integer;
			templ: Longint;
			oldPort: GrafPtr;

		BEGIN
			theItem:=LoWord(mResult);
			theMenu:=HiWord(mResult);

			CASE theMenu OF

				mApple:
					IF (theItem=iAboutMe) THEN
						ShowAboutMeDialog
					ELSE BEGIN
						GetItem(GetMHandle(mApple),theItem,name);
						GetPort(oldPort);
						temp:=OpenDeskAcc(name);
						SetPort(oldPort);
					END;
				mFile:
					CASE theItem OF
						iQuit: doneFlag:=TRUE;
					END;
				mEdit: BEGIN
					IF NOT SystemEdit(theItem-1) THEN;
				END;
				mSFile: BEGIN
					CASE theItem OF
						iNormalGet:					doNormalGet;
						iNormalPut:					doNormalPut;
						iNormalPGet:				doNormalPGet;
						iNormalPPut:				doNormalPPut;
						iFileFilter:				doFileFilter;
						iGetDirectory:			doGetDirectory;
						iMultiFile:					doMultiFile;
						iListsNPutFile:			doListsNPutFile;
						iPutOptions:				doPutOptions;
						iIdleUpdates:				doIdleUpdates;
						iForceDirectory:		doForceDirectory;
					END;
				END;
			END;
			HiliteMenu(0);
		END;


	BEGIN

		UnLoadSeg(@_DataInit);
		MaxApplZone;
		InitGraf(@thePort);
		InitFonts;
		FlushEvents(everyEvent,0);
		InitWindows;
		InitMenus;
		TEInit;
		InitDialogs(NIL);
		InitCursor;

		dummy:=SysEnvirons(1,theWorld);
		WNEIsImplemented:=((theWorld.machineType>=0) AND (NGetTrapAddress(_WaitNextEvent,
											 ToolTrap)<>NGetTrapAddress(_Unimplemented,ToolTrap)));

		flagPtr := PtrToWord(kHWCfgFlags);
		IF BAnd(flagPtr^,$0200) <> 0 THEN BEGIN
			haveAUX := TRUE;
		END ELSE BEGIN
			haveAUX := FALSE;
		END;

		SetUpMenus;
		doneFlag:=FALSE;

		SFSaveDisk := PtrToWord(kSFSaveDisk);
		CurDirStore := PtrToLong(kCurDirStore);
		TopMapHndl := PtrToLong(kTopMapHndl);
		MySaveDisk := SFSaveDisk^ + 1; {so we're sure that they're different}

		REPEAT
			IF WNEIsImplemented THEN
				haveEvent:=WaitNextEvent(everyEvent,myEvent,$7FFFFFFF,NIL)
			ELSE BEGIN
				SystemTask;
				haveEvent:=GetNextEvent(everyEvent,myEvent);
			END;

			deskPart := FindWindow(myEvent.where,whichWindow);
			IF deskPart <> inSysWindow THEN
				InitCursor;

			IF haveEvent THEN BEGIN
				CASE myEvent.what OF
					mouseDown:
						CASE deskPart OF
							inSysWindow:
								SystemClick(myEvent,whichWindow);
							inMenuBar:
								DoCommand(MenuSelect(myEvent.where));
						END;
					keyDown,autoKey:
							IF BAND(myEvent.modifiers,cmdKey)<>0 THEN
								DoCommand(MenuKey(CHR(BAND(myEvent.message,charCodeMask))))
				END;
			END;
		UNTIL doneFlag;
	END.
